package com.uduemc.biso.node.web.service.impl;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.uduemc.biso.core.utils.RestResult;
import com.uduemc.biso.core.utils.ResultUtil;
import com.uduemc.biso.node.core.backend.feign.WebBackendFeign;
import com.uduemc.biso.node.core.common.utils.AssetsUtil;
import com.uduemc.biso.node.core.property.GlobalProperties;
import com.uduemc.biso.node.core.utils.Ip2regionUtil;
import com.uduemc.biso.node.web.service.Ip2regionService;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class Ip2regionServiceImpl implements Ip2regionService {

	@Autowired
	private GlobalProperties globalProperties;

	@Autowired
	private WebBackendFeign webBackendFeign;

	public static Searcher SearcherObj = null;

	public static String DbPathStr = null;

	@Override
	public String xdbPath() {
		String ip2regionPath = AssetsUtil.getIp2regionPath(globalProperties.getSite().getAssetsPath());
		File ip2regionFile = new File(ip2regionPath);
		if (!ip2regionFile.isDirectory()) {
			FileUtil.mkdir(ip2regionFile);
		}
		return ip2regionPath;
	}

	@Override
	public File xdbFile() throws JsonParseException, JsonMappingException, JsonProcessingException, IOException {
		String xdbPath = xdbPath();
		File[] ls = FileUtil.ls(xdbPath);
		if (ArrayUtil.isNotEmpty(ls)) {
			File lastVersion = null;
			long modified = -1;
			for (File file : ls) {
				long lastModified = file.lastModified();
				if (lastModified > modified) {
					modified = lastModified;
					lastVersion = file;
				}
			}
			return lastVersion;
		}

		RestResult restResult = webBackendFeign.ip2regionUpdateLastVersion();
		String version = ResultUtil.data(restResult, String.class);
		if (StrUtil.isNotBlank(version)) {
			ls = FileUtil.ls(xdbPath);
			if (ArrayUtil.isNotEmpty(ls)) {
				File lastVersion = null;
				long modified = -1;
				for (File file : ls) {
					long lastModified = file.lastModified();
					if (lastModified > modified) {
						modified = lastModified;
						lastVersion = file;
					}
				}
				return lastVersion;
			}
		}

		return null;
	}

	@Override
	public String region(String ip) throws JsonParseException, JsonMappingException, JsonProcessingException, IOException {
		File xdbFile = xdbFile();
		if (xdbFile == null || !xdbFile.isFile()) {
			log.error("xdbFile emtpy xdbFile: " + xdbFile.getAbsolutePath());
			return "";
		}

		String dbPath = xdbFile.getAbsolutePath();
		if (SearcherObj == null) {
			DbPathStr = dbPath;
			// 1、从 dbPath 加载整个 xdb 到内存。
			byte[] cBuff;
			try {
				cBuff = Searcher.loadContentFromFile(dbPath);
			} catch (Exception e) {
				log.error("failed to load content from " + dbPath + ": " + e);
				return "";
			}
			// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
			if (SearcherObj == null) {
				try {
					SearcherObj = Searcher.newWithBuffer(cBuff);
				} catch (Exception e) {
					log.error("failed to create content cached searcher: " + e);
					return "";
				}
			}
		} else {
			if (!dbPath.equals(DbPathStr)) {
				DbPathStr = dbPath;
				// 1、从 dbPath 加载整个 xdb 到内存。
				byte[] cBuff;
				try {
					cBuff = Searcher.loadContentFromFile(dbPath);
				} catch (Exception e) {
					log.error("failed to load content from " + dbPath + ": " + e);
					return "";
				}
				// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
				if (SearcherObj == null) {
					try {
						SearcherObj = Searcher.newWithBuffer(cBuff);
					} catch (Exception e) {
						log.error("failed to create content cached searcher: " + e);
						return "";
					}
				}
			}
		}

		// 3、查询
		String region = "";
		try {
			long sTime = System.nanoTime();
			region = SearcherObj.search(ip);
			long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
			log.info("{region: " + region + ", ioCount: " + SearcherObj.getIOCount() + ", took: " + cost + " μs}");
		} catch (Exception e) {
			log.error("failed to search(" + ip + "): " + e);
		}

		// 备注：并发使用，用整个 xdb 数据缓存创建的查询对象可以安全的用于并发，也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。

		return region;
	}

	@Override
	public String ip4Country(String ip) throws JsonParseException, JsonMappingException, JsonProcessingException, IOException {
		return Ip2regionUtil.country(region(ip));
	}

}
